/*
* QR.cs
* 
* Copyright (c) 2006, dnAnalytics. All rights reserved.
*/

using System;
using dnAnalytics.Math;
using dnAnalytics.Resources;
using dnAnalytics.Exceptions;

namespace dnAnalytics.LinearAlgebra.Decomposition
{
    /// <summary>
    /// Computes the QR decomposition of a <see cref="Matrix"/>.
    /// </summary>
    public sealed class QR : Algorithm
    {
        #region Fields

        private IQR decomp;
        private readonly int rows;
        private readonly int columns;

        #endregion Fields

        #region Constructor
        /// <summary>
        /// Constructs an QR object for the given matrix.
        /// </summary>
        /// <param name="matrix">The matrix to factor.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="matrix"/> is <b>null</b>.</exception>
        /// <exception cref="NotSupportedException">If the library is running on MS Windows and using <b>CLAPACK</b>
        /// as the <see cref="dnAnalytics.LinearAlgebra.Native.LapackProvider"/>.</exception>
        /// <remarks><b>This class is not supported under MS Windows when using 
        /// the CLAPACK library.</b></remarks>
        public QR(Matrix matrix)
        {
            if (Settings.UseNativeLibrary && Settings.Lapack == dnAnalytics.LinearAlgebra.Native.LapackProvider.Clapack && Environment.OSVersion.Platform != PlatformID.Unix)
            {
                throw new NotSupportedException(Strings.QRNotSupported);
            }
            if (matrix == null)
            {
                throw new ArgumentNullException("matrix", Strings.NullParameterException);
            }

            decomp = new DenseQR(matrix);
            rows = matrix.Rows;
            columns = matrix.Columns;
        }

        #endregion Constructor

        #region Public Methods

        /// <summary>
        /// Returns the orthogonal Q matrix.
        /// </summary>
        /// <returns>The orthogonal Q matrix.</returns>
        public Matrix Q()
        {
            return decomp.Q();
        }

        /// <summary>
        /// Returns the upper triangular factor R.
        /// </summary>
        /// <returns>The upper triangular factor R.</returns>
        public Matrix R()
        {
            return decomp.R();
        }

        /// <summary>
        /// Copies the orthogonal Q matrix into the result matrix.
        /// </summary>
        /// <param name="result">A matrix to copy the orthogonal Q matrix into.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="result"/> is <b>null</b>.</exception>
        /// <exception cref="NotConformableException">If <paramref name="result"/> is not a square matrix 
        /// with an order equal to the number of rows of the matrix being factored.</exception>
        public void Q(Matrix result)
        {
            if (result == null)
            {
                throw new ArgumentNullException("result", Strings.NullParameterException);
            }
            if (result.Rows != rows || result.Columns != rows)
            {
                throw new NotConformableException("result", Strings.ParameterNotConformable);
            }
            decomp.Q(result);
        }

        /// <summary>
        /// Copies the upper triangular factor R into the result matrix.
        /// </summary>
        /// <param name="result">A matrix to copy upper triangular factor R into.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="result"/> is <b>null</b>.</exception>
        /// <exception cref="NotConformableException">If <paramref name="result"/> does not have the
        /// dimensions m by n, where m and n are the number of columns and rows of the matrix being
        /// factored, respectively.</exception>
        public void R(Matrix result)
        {
            if (result == null)
            {
                throw new ArgumentNullException("result", Strings.NullParameterException);
            }
            if (result.Rows != rows || result.Columns != columns)
            {
                throw new NotConformableException("result", Strings.ParameterNotConformable);
            }

            decomp.R(result);
        }

        /// <summary>
        /// Determines whether the matrix is full rank or not.
        /// </summary>
        /// <returns><b>true</b> if the matrix is full rank; otherwise <b>false</b>.</returns>         
        public bool IsFullRank()
        {
            return decomp.IsFullRank();
        }

        ///<summary>Calculates the determinant (absolute value) of the matrix.</summary>
        ///<returns>The determinant of the matrix.</returns>
        ///<exception cref="NotSquareMatrixException">If the matrix is not square.</exception>
        public double Determinant()
        {
            if (rows != columns)
            {
                throw new NotSquareMatrixException();
            }
            return decomp.Determinant();
        }


        /// <summary>
        /// Finds the least squares solution of <b>A*X = B</b>, where <b>A.Rows &gt;= A.Columns</b>
        /// </summary>
        /// <param name="rightHandSide">The right hand side <see cref="Matrix"/>, <b>B</b>.</param>
        /// <returns>The left hand side <see cref="Matrix"/>, <b>X</b>.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="rightHandSide"/> is <b>null</b>.</exception>
        /// <exception cref="NotConformableException">If the number of rows of the <paramref name="rightHandSide"/> 
        /// matrix differs from the number of rows of the decomposed matrix.</exception>
        /// <exception cref="InvalidOperationException">The decomposed matrix is rank deficient or its number of <b>A.Rows &lt;= A.Columns</b>.</exception>
        public Matrix Solve(Matrix rightHandSide)
        {
            if (rightHandSide == null)
            {
                throw new ArgumentNullException("rightHandSide", Strings.NullParameterException);
            }

            if (rows != rightHandSide.Rows)
            {
                throw new NotConformableException("rightHandSide", Strings.ParameterNotConformable);
            }

            if (rows < columns)
            {
                throw new InvalidMatrixOperationException(Strings.RowsLessThanColumns);
            }

            if (!IsFullRank())
            {
                throw new InvalidMatrixOperationException(Strings.NotFullRank);
            }

            return decomp.Solve(rightHandSide);
        }

        /// <summary>
        /// Finds the least squares solution of <b>A*X = B</b>, where <b>A.Rows &gt;= A.Columns</b>
        /// </summary>
        /// <param name="rightHandSide">The right hand side <see cref="Matrix"/>, <b>B</b>.</param>
        /// <param name="result">The left hand side <see cref="Matrix"/>, <b>X</b>.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="rightHandSide"/> or <paramref name="result"/> 
        /// is <b>null</b>.</exception>
        /// <exception cref="NotConformableException"><para>If the number of rows of the <paramref name="rightHandSide"/> 
        /// matrix differs from the number of rows of the decomposed matrix.</para>
        /// <para> - or - </para>
        /// <para>If the <paramref name="rightHandSide"/> matrix has a different number of columns than
        /// the <paramref name="result"/> matrix.</para>
        /// <para> - or - </para>
        /// <para>If the <paramref name="result"/> matrix has a different number of rows than
        /// the number of columns of the decomposed matrix.</para></exception>
        /// <exception cref="InvalidOperationException">The decomposed matrix is rank deficient or <b>A.Rows &lt;= A.Columns</b>.</exception>
        public void Solve(Matrix rightHandSide, Matrix result)
        {
            if (rightHandSide == null)
            {
                throw new ArgumentNullException("rightHandSide", Strings.NullParameterException);
            }

            if (result == null)
            {
                throw new ArgumentNullException("result", Strings.NullParameterException);
            }

            if (rows != rightHandSide.Rows)
            {
                throw new NotConformableException("rightHandSide", Strings.ParameterNotConformable);
            }

            if (columns != result.Rows)
            {
                throw new NotConformableException("result", Strings.ParameterNotConformable);
            }

            if (rightHandSide.Columns != result.Columns)
            {
                throw new NotConformableException(Strings.ParametersNotConformable);
            }

            if (rows < columns)
            {
                throw new InvalidMatrixOperationException(Strings.RowsLessThanColumns);
            }

            if (!IsFullRank())
            {
                throw new InvalidMatrixOperationException(Strings.NotFullRank);
            }

            decomp.Solve(rightHandSide, result);
        }


        /// <summary>
        /// Finds the least squares solution of <b>A*x = b</b>, where <b>A.Rows &gt;= A.Columns</b>
        /// </summary>
        /// <param name="rightHandSide">The right hand side vector, <b>b</b>.</param>
        /// <returns>The left hand side <see cref="Vector"/>, <b>x</b>.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="rightHandSide"/> is <b>null</b>.</exception>
        /// <exception cref="NotConformableException">If the size of <paramref name="rightHandSide"/> does not equal
        /// the number of rows of the decomposed matrix.</exception>
        /// <exception cref="InvalidOperationException">The decomposed matrix is rank deficient or its number of <b>A.Rows &lt;= A.Columns</b>.</exception>
        public Vector Solve(Vector rightHandSide)
        {
            if (rightHandSide == null)
            {
                throw new ArgumentNullException("rightHandSide", Strings.NullParameterException);
            }

            if (rows < columns)
            {
                throw new InvalidMatrixOperationException(Strings.RowsLessThanColumns);
            }

            if (rightHandSide.Count != rows)
            {
                throw new NotConformableException("rightHandSide", Strings.ParameterNotConformable);
            }

            if (!IsFullRank())
            {
                throw new InvalidMatrixOperationException(Strings.NotFullRank);
            }

            return decomp.Solve(rightHandSide);

        }


        /// <summary>
        /// Finds the least squares solution of <b>A*x = b</b>, where <b>A.Rows &gt;= A.Columns</b>
        /// </summary>
        /// <param name="rightHandSide">The right hand side <see cref="Vector"/>, <b>b</b>.</param>
        /// <param name="result">The left hand side <see cref="Vector"/>, <b>x</b>.</param>
        /// <exception cref="ArgumentNullException">If either <paramref name="rightHandSide"/> or <paramref name="result"/>
        /// is <b>null</b>.</exception>
        /// <exception cref="NotConformableException"><para>If the size of <paramref name="rightHandSide"/> does not equal
        /// the number of rows of the decomposed matrix.</para>
        /// <para> - or - </para>
        /// <para>If the size of <paramref name="result"/> does not equal
        /// the number of columns of the decomposed matrix.</para>
        /// </exception>
        /// <exception cref="InvalidOperationException">The decomposed matrix is rank deficient or its number of <b>A.Rows &lt;= A.Columns</b>.</exception>
        public void Solve(Vector rightHandSide, Vector result)
        {
            if (rightHandSide == null)
            {
                throw new ArgumentNullException("rightHandSide", Strings.NullParameterException);
            }
            if (result == null)
            {
                throw new ArgumentNullException("result", Strings.NullParameterException);
            }

            if (rows < columns)
            {
                throw new InvalidMatrixOperationException(Strings.RowsLessThanColumns);
            }

            if (rightHandSide.Count != rows)
            {
                throw new NotConformableException("rightHandSide", Strings.ParameterNotConformable);
            }

            if (result.Count != columns)
            {
                throw new NotConformableException("result", Strings.ParameterNotConformable);
            }

            if (!IsFullRank())
            {
                throw new InvalidMatrixOperationException(Strings.NotFullRank);
            }

            decomp.Solve(rightHandSide, result);
        }

        #endregion Public Methods

        #region Protected Members

        /// <summary>
        /// Delegates the actual computation to the correct QR subtype.
        /// </summary>
        protected override void InternalCompute()
        {
            decomp.Compute();
        }
        #endregion Protected Members
    }
}
